Lektion 28



Bezier Flächen (Bezier Patches)

Geschrieben von: David Nikdel ( ogapo@ithink.net. )

Dieses Tutorial soll Sie in Bezier Flächen einführen, in der Hoffnung, dass jemand der künstlerisch mehr begabt als ich ist, etwas ziemlich cooles damit macht und es uns zeigt. Es ist nicht als eine komplette Bezier Patch Bibliothek beabsichtig, sondern viel mehr als Einstiegspunkt, um Sie damit vertraut zu machen, wie gekrümmte Oberflächen arbeiten. Außerdem, da dies hier nur zu Information dient, habe ich nicht die ganz korrekte Terminologie zugunsten des Verständnisses benutzt; Ich hoffe, dass jeder damit einverstanden ist. Für jeden, der bereits mit Beziers vertraut ist und das hier nur liest, um zu sehen, ob ich es verhunzt habe: schämen Sie sich ;-), aber wenn Sie etwas völlig falsches finden, lassen sie es mich oder NeHe wissen, da ja keiner perfekt ist, oder nicht? Oh, noch eine weitere Sache: der komplette Code wurde in keinster Weise über meinen normalen Programmierstil hinaus optimiert. Ich möchte, dass jeder genau weiß, was vorgeht. Nun denn, ich denke das ist genug zur Einführung. Auf geht's!

Die Mathematik - ::dämonische Musik:: (achtung, etwas längerer Abschnitt)

Ok, es dürfte recht schwierig sein Beziers zu verstehen, ohne die grundlegendsten Verständnisse der Mathematik, die dahinter steht, wie dem auch sei, wenn Sie diesen Teil gerade nicht lesen wollen oder Sie bereits die Mathematik kennen, können Sie diesen Teil überspringen. Ich werde mit der Beschreibung beginnen, was eine Bezier Kurve ist und dann dahin übergehen, wie man ein Bezier Patch erzeugt.

Komisch ist, dass, wenn Sie jemals ein Graphic-Programm verwendet haben, Sie bereits mit Bezier Kurven vertraut sind, wenn auch wahrscheinlich nicht unter diesem Namen. Sie sind die verbreitetste Methode um krumme Linien zu zeichnen und werden in der Regel durch eine Reihe von Punkten repräsentiert, wobei jeder 2 Punkte hat, die die Tangente repräsentieren, am Punkt links und rechts davon. So sieht das aus:



Das ist die einfachst mögliche Bezier Kurve (längere werden durch viele kleine gebildet (häufig ohne dass der Benutzer es merkt)). Diese Kurve ist tatsächlich nur durch 4 Punkte definiert, das wären die 2 End-Kontroll-Punkte und die 2 mittleren Kontroll-Punkte. Für den Computer sind alle Punkte gleich, aber um uns das Designen zu erleichtern verbinden wir jeweils die ersten und letzten zwei, da diese immer Tangenten zum Endpunkt sind. Die Kurve ist eine parametrische Kurve und wird gezeichnet, indem Punkte, die direkt auf der Kuve liegen, mit geraden Linien verbunden werden. Auf diesem Weg können Sie die Auflösung des Patch kontrollieren (und den rechen Aufwand). Der gebräuchlichste Weg ist es bei höherer Entfernung weniger Details zu verwenden und je näher der Betrachter kommt, entsprechend mehr, so dass es immer nach einer perfekt gekrümmten Oberfläche aussieht, mit dem geringsten Rechenaufwand.

Bezier Kurven basieren auf einer Basis-Funktion, von der kompliziertere Versionen abgeleitet werden. Hier die Funktion:

t + (1 - t) = 1

Klingt einfach, was? Nunja, ist es auch, das ist die einfachste Bezier Kurve, eine Kurve ersten Grades. Wie Sie vielleicht aus der Terminologie erkennen können, handelt es sich bei Bezier Kurven um Polynome und wie wir uns aus der Algebra erinnen, ein Polynom ersten Grades ist einfach nur eine gerade Linie; nicht gerade interessant. Nun, da die Basis-Funktion für alle Nummer t wahr ist, können wir quadrieren, sie in die dritte Potenz erheben, was auch immer, es würde immer noch wahr sein, richtig? Versuchen wir's mit der dritten Potenz.

(t + (1-t))^3 = 1^3

t^3 + 3*t^2*(1-t) + 3*t*(1-t)^2 + (1-t)^3 = 1

Das ist die Gleichung die wir benutzen werden, um das gängiste Bezier zu berechnen, die Bezier Kurve dritten Grades. Sie ist am gängisten für zwei Gründe, a) es ist das Polynomal des kleinsten Grades, das nicht notwendigerweise in einer Ebene lieben muss (es gibt 4 Kontrollpunkte) und b) die Tangenten Linien an den Seiten sind nicht voneinander abhängig (bei 2ten Grades gäbe es nur 3 Kontrollpunkte). Sehen Sie bereits die Bezier Kurve? Hehe, ich auch nicht, deshalb muss ich eine Sache noch hinzufügen.

Ok, da die gesamte linke Seite gleich 1 ist, ist es sicher anzunehmen, dass wenn Sie alle Komponenten addieren, Sie immer noch gleich 1 sind. Hört sich das danach, ob wir damit entscheiden können, wieviel jeder Kontrollpunkt benutzt wird, um einen Punkt in der Kurve zu berechnen? (hinweise: sagen Sie einfach ja ;-) ) Sie haben recht! Wenn wir den Wert eines Punktes einige Prozent entlängs der Kurve berechnen wollen, multiplizieren wir einfach jeden Teil mit einem Kontrollpunkt (als Vektor) und finden die Summe. Generell kann man sagen, wir arbeiten mit 0 <= t <= 1, aber das ist technisch gesehen nicht notwendig. Verwirrt? Hier ist die Funktion:

P1*t^3 + P2*3*t^2*(1-t) + P3*3*t*(1-t)^2 + P4*(1-t)^3 = Pneu

Da Polynome immer stetig sind, ist das ein guter Weg, um zwischen den 4 Punkten zu morphen. Die einzigen Punkte die tatsächlich erreicht werden, sind P1 und P4 wenn t = 1, bzw. gleich 0 ist.

Nun, das ist alles schön und gut, aber wie kann ich das in 3D verwenden werden Sie sich fragen. Nun, eigentlich ist es recht einfach, um eine Bezier Patch zu formen, brauchen Sie 16 Kontrollpunkte (4*4) und 2 Variablen t und v. Daraus berechnen Sie ein Punkte bei v entlängs der 4 parallelen Kurven, dann benutzen Sie diese 4 Punkte um eine neue Kurve zu machen und berechnen t entlängs dieser Kurve. Indem wir genügend dieser Punkte berechnen, können wir Triangle Strips zeichnen und diese miteinander verbinden, woraus unser Bezier Patch ensteht.

   

Ich denke das war erst mal genug Mathe für jetzt, auf zum Code!

#include < windows.h>							// Header Datei für Windows
#include < math.h>							// Header Datei für Math Library Routines
#include < stdio.h>							// Header Datei für Standard I/O Routinen
#include < stdlib.h>							// Header Datei für Standard Library
#include < gl\gl.h>							// Header Datei für die OpenGL32 Library
#include < gl\glu.h>							// Header Datei für die GLu32 Library
#include < gl\glaux.h>							// Header Datei für dieGlaux Library

typedef struct point_3d {						// Struktur für einen 3-Dimensionalen Punkt ( NEU )
	double x, y, z;
} POINT_3D;

typedef struct bpatch {							// Struktur für ein Bezier Patch 3ten Grades ( NEU )
	POINT_3D	anchors[4][4];					// 4x4 Gitter an Anker-Punkten
	GLuint		dlBPatch;					// Display Liste für Bezier Patch
	GLuint		texture;					// Textur für das Patch
} BEZIER_PATCH;

HDC			hDC=NULL;					// Privater GDI Device Context
HGLRC			hRC=NULL;					// Permanenter Rendering Context
HWND			hWnd=NULL;					// Enthält unser Fenster-Handle
HINSTANCE		hInstance;					// Enthält die Instanz der Applikation

DEVMODE			DMsaved;					// speichert die vorherigen Bildschirmeinstellungen ( NEU )

bool			keys[256];					// Array das für die Tastatur Routine verwendet wird
bool			active=TRUE;					// Fenster Aktiv Flag standardmäßig auf TRUE gesetzt
bool			fullscreen=TRUE;				// Fullscreen Flag ist standardmäßig auf TRUE gesetzt

GLfloat			rotz = 0.0f;					// Rotation um die Z-Achse
BEZIER_PATCH		mybezier;					// Das Bezier Patch welches wir verwenden werden ( NEU )
BOOL			showCPoints=TRUE;				// schaltet das Anzeigen des Kontroll-Punkt-Gitters ( NEU )
int			divs = 7;					// Anzahl der Intrapolationen(steuert Polygon Auflösung) ( NEU )

LRESULT	CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);			// Deklaration für WndProc

Die folgenden Funktionen sind nur ein paar kleine Funktionen für einfache Vektor-Mathematik. Wenn Sie ein Fan von C++ sind, könnten Sie auch eine Punkt-Klasse in betracht ziehen (stellen Sie sicher, dass diese 3D ist).

// Addiert 2 Punkte. Benutzen Sie nicht einfach '+' ;)
POINT_3D pointAdd(POINT_3D p, POINT_3D q) {
	p.x += q.x;		p.y += q.y;		p.z += q.z;
	return p;
}

// Multiplziert einen Punkt un eine Konstante. Benutzen Sie nicht einfach '*'
POINT_3D pointTimes(double c, POINT_3D p) {
	p.x *= c;	p.y *= c;	p.z *= c;
	return p;
}

// Funktion zur schnellen Punkt-Erzeugung
POINT_3D makePoint(double a, double b, double c) {
	POINT_3D p;
	p.x = a;	p.y = b;	p.z = c;
	return p;
}

Das ist grundlegend nur die Basis-Funktion 3ten Grades in C. Ihr wird eine Variable u und ein Array von 4 Punkten übergeben und berechnet dann einen Punkt auf der Kurve. Indem u in gleichen Schritten zwischn 0 und 1 erhöht wird, erhalten wir eine nette Annäherung der Kurve.

// berechnet Polygon dritten Grades basierend auf einem Array aus 4 Punkten
// und einer einzelnen Variable (u) welche in der Regel zwischen 0 und 1 ist
POINT_3D Bernstein(float u, POINT_3D *p) {
	POINT_3D	a, b, c, d, r;

	a = pointTimes(pow(u,3), p[0]);
	b = pointTimes(3*pow(u,2)*(1-u), p[1]);
	c = pointTimes(3*u*pow((1-u),2), p[2]);
	d = pointTimes(pow((1-u),3), p[3]);

	r = pointAdd(pointAdd(a, b), pointAdd(c, d));

	return r;
}
 

Diese Funktion macht den Löwenanteil der Arbeit, indem Sie alle Triangle Strips (Dreiecks Streifen) erzeugt und in einer Display Liste speichert. Wir machen das, damit wir den Patch nicht jeden Frame erneut berechnen müssen, sondern nur dann, wenn er sich ändert. Nebenbei, ein cooler Effekt wäre es, wenn Sie versuchen würden das Morphing Tutorial zu verwenden und die Kontrollpunkte des Patch's morphen würden. Das würde einen coolen weichen, organischen Morphing-Effekt ergeben, für realtiv wenig Overhead (da Sie nur 16 Punkte morphen müssen, aber neu berechnen müssten). Das 'last' Array wird benutzt, um die vorherogen Linien an Punkten zu behalten (da ein Triangle Strip beide Reihen benötigt). Außerdem werden die Texturkoordinaten berechnet, indem die Werte u und v als Prozente verwendet werden (planares Mapping).

Was wir nicht machen ist die Berechnung des Normalenvektors für die Beleuchtung. Wenn es dazu kommt haben Sie grundsätzlich zwei Möglichkeiten. Die erste ist, die Mitte jeden Dreiecks zu finden, dann benutzten Sie etwas Matematik und berechnen die Tangente auf der X und Y-Achse, dann bilden Sie das Kreuzprodukt, damit die Vektoren senkrecht zueinander stehen, DANN normalisieren Sie den Vektor und benutzen dies als den Normalenvektor. ODER (ja, es gibt einen schnelleren Weg) Sie schummeln etwas und benutzen einfach den Normalenvektor des Dreiecks (berechnen Sie es, wie Sie es bevorzugen), um eine ziemlich gute Annäherug zu erhalten. Ich bevorzuge letzteres; der Geschwindigkeitsvorteil ist meiner Meinung nach vorzuziehen, da es das klein wenige extra Realismus nicht Wert wäre.

// erzeugt eine Display Liste basierend auf den Daten aus dem Patch
// und der Anzahl der Divisionen
GLuint genBezier(BEZIER_PATCH patch, int divs) {
	int		u = 0, v;
	float		py, px, pyold;
	GLuint		drawlist = glGenLists(1);			// erzeuge die Display Liste
	POINT_3D	temp[4];
	POINT_3D	*last = (POINT_3D*)malloc(sizeof(POINT_3D)*(divs+1));
				// Array an Punkten, um die erste Linie Polygone zu kennzeichnen

	if (patch.dlBPatch != NULL)					// werde alle alten Display Listen los
		glDeleteLists(patch.dlBPatch, 1);

	temp[0] = patch.anchors[0][3];					// die erste hergeleitete Kurve (entlängs der X-Achse)
	temp[1] = patch.anchors[1][3];
	temp[2] = patch.anchors[2][3];
	temp[3] = patch.anchors[3][3];

	for (v=0;v<=divs;v++) {						// erzeuge die erste Linien an Punkten
		px = ((float)v)/((float)divs);				// Prozent entlängs der Y-Achse
	// benutze die 4 Punkte aus der hergeleiteten Kurve, um die Punkte entlängs der Kurve zu berechnen
		last[v] = Bernstein(px, temp);
	}

	glNewList(drawlist, GL_COMPILE);				// Starte eine neue Display Liste
	glBindTexture(GL_TEXTURE_2D, patch.texture);			// Binde die Texturen

	for (u=1;u<=divs;u++) {
		py    = ((float)u)/((float)divs);			// Prozent entlängs der Y-Achse
		pyold = ((float)u-1.0f)/((float)divs);			// Prozent entlängs der alten Y-Achse

		temp[0] = Bernstein(py, patch.anchors[0]);		// berechne neue Bezier Punkte
		temp[1] = Bernstein(py, patch.anchors[1]);
		temp[2] = Bernstein(py, patch.anchors[2]);
		temp[3] = Bernstein(py, patch.anchors[3]);

		glBegin(GL_TRIANGLE_STRIP);				// Beginne einen neuen Triangle Strip

		for (v=0;v<=divs;v++) {
			px = ((float)v)/((float)divs);			// Prozent entlängs der X-Achse

			glTexCoord2f(pyold, px);			// wende die alten Texturkoordinaten an
			glVertex3d(last[v].x, last[v].y, last[v].z);	// alter Punkt

			last[v] = Bernstein(px, temp);			// erzeuge neuen Punkt
			glTexCoord2f(py, px);				// wende die neuen Texturkoordinaten an
			glVertex3d(last[v].x, last[v].y, last[v].z);	// Neuer Punkt
		}

		glEnd();						// BEENDE Triangle Strip
	}

	glEndList();							// BEENDE die Liste

	free(last);							// gebe das alte Vertices-Array frei
	return drawlist;						// gebe die Display Liste zurück
}

Hier laden wir lediglich die Matrix mit ein paar Werten, die ich gewählt habe, weil ich denke, dass sie cool aussehen. Spielen Sie einfach damit herum und schauen Sie, was dabei herauskommt. :-)

void initBezier(void) {
	mybezier.anchors[0][0] = makePoint(-0.75,	-0.75,	-0.50);	// Setze die Bezier Vertices
	mybezier.anchors[0][1] = makePoint(-0.25,	-0.75,	 0.00);
	mybezier.anchors[0][2] = makePoint( 0.25,	-0.75,	 0.00);
	mybezier.anchors[0][3] = makePoint( 0.75,	-0.75,	-0.50);
	mybezier.anchors[1][0] = makePoint(-0.75,	-0.25,	-0.75);
	mybezier.anchors[1][1] = makePoint(-0.25,	-0.25,	 0.50);
	mybezier.anchors[1][2] = makePoint( 0.25,	-0.25,	 0.50);
	mybezier.anchors[1][3] = makePoint( 0.75,	-0.25,	-0.75);
	mybezier.anchors[2][0] = makePoint(-0.75,	 0.25,	 0.00);
	mybezier.anchors[2][1] = makePoint(-0.25,	 0.25,	-0.50);
	mybezier.anchors[2][2] = makePoint( 0.25,	 0.25,	-0.50);
	mybezier.anchors[2][3] = makePoint( 0.75,	 0.25,	 0.00);
	mybezier.anchors[3][0] = makePoint(-0.75,	 0.75,	-0.50);
	mybezier.anchors[3][1] = makePoint(-0.25,	 0.75,	-1.00);
	mybezier.anchors[3][2] = makePoint( 0.25,	 0.75,	-1.00);
	mybezier.anchors[3][3] = makePoint( 0.75,	 0.75,	-0.50);
	mybezier.dlBPatch = NULL;					// mache weiter und initialisiere dieses mit NULL
}
 

Dies ist lediglich eine optimierte Routine um ein einzelnen Bitmap zu laden. Sie kann einfach verwendet werden, um ein Array davon zu laden, indem Sie einfach eine einfache Schleife verwenden.

// Lade Bitmaps und konvertiere in Texturen

BOOL LoadGLTexture(GLuint *texPntr, char* name)
{
	BOOL success = FALSE;
	AUX_RGBImageRec *TextureImage = NULL;

	glGenTextures(1, texPntr);					// erzeuge 1 Textur

	FILE* test=NULL;
	TextureImage = NULL;

	test = fopen(name, "r");					// Teste, ob die Datei existiert
	if (test != NULL) {						// wenn ja
		fclose(test);						// schließe die Datei
		TextureImage = auxDIBImageLoad(name);			// und lade die Textur
	}

	if (TextureImage != NULL) {					// wenn sie geladen wurde
		success = TRUE;

		// normalerweise benutzt die Textur-Erzeugung Daten aus dem Bitmap
		glBindTexture(GL_TEXTURE_2D, *texPntr);
		glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage->sizeX, 
TextureImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, 
TextureImage->data);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	}

	if (TextureImage->data)
		free(TextureImage->data);

	return success;
}

Fügen Sie die Patch Initialisierung einfach hier ein. Sie würden das immer dann machen, wenn Sie ein Patch erzeugen. Erneut wäre das ein cooler Platz um C++ einzusetzen (Bezier Klasse?).

int InitGL(GLvoid)							// Der ganze Setup Kram für OpenGL kommt hier rein
{
	glEnable(GL_TEXTURE_2D);					// aktiviert Textur Mapping
	glShadeModel(GL_SMOOTH);					// aktiviert Smooth Shading
	glClearColor(0.05f, 0.05f, 0.05f, 0.5f);			// Schwarzer Hintergrund
	glClearDepth(1.0f);						// Depth Buffer Setup
	glEnable(GL_DEPTH_TEST);					// aktiviert Depth Testing
	glDepthFunc(GL_LEQUAL);						// Die Art des auszuführenden Depth Test
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);		// wirklich nette Perspektiven Berechnungen

	initBezier();							// Initialisiert das Bezier Kontroll-Gitter ( NEU )
	LoadGLTexture(&(mybezier.texture), "./Data/NeHe.bmp");		// Lade die Textur ( NEU )
	mybezier.dlBPatch = genBezier(mybezier, divs);			// erzeuge Patch ( NEU )

	return TRUE;							// Initialisierung war in Ordnung
}

Als erstes rufen Sie die Bezier Display Liste auf. Dann (wenn die Umriße angeschaltet sind), zeichne die Linien, die die Kontroll-Punkte verbinden. Sie können diese an und ausschalten, indem Sie die LEERTASTE drücken.

int DrawGLScene(GLvoid)	{						// Hier kommt der ganze Zeichnen-Kram hin
	int i, j;
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Lösche den Bildschirm und den Depth-Buffer
	glLoadIdentity();						// Resette die aktuelle Modelview Matrix
	glTranslatef(0.0f,0.0f,-4.0f);					// 1.5 Einheiten nach Links dann 6.0 Einheiten in den Bildschirm hinein
	glRotatef(-75.0f,1.0f,0.0f,0.0f);
	glRotatef(rotz,0.0f,0.0f,1.0f);					// Rotiere das Dreieck auf der Z-Achse

	glCallList(mybezier.dlBPatch);					// rufe die Bezier Display Liste auf
									// Dies muss nur aktualisiert werden, wenn das Patch geändert wird

	if (showCPoints) {						// wenn das Gitter gezeichnet werden soll
		glDisable(GL_TEXTURE_2D);
		glColor3f(1.0f,0.0f,0.0f);
		for(i=0;i<4;i++) {					// zeichne die horizontalen Linien
			glBegin(GL_LINE_STRIP);
			for(j=0;j<4;j++)
				glVertex3d(mybezier.anchors[i][j].x, mybezier.anchors[i][j].y, mybezier.anchors[i][j].z);
			glEnd();
		}
		for(i=0;i<4;i++) {					// zeichne die vertikalen Linien
			glBegin(GL_LINE_STRIP);
			for(j=0;j<4;j++)
				glVertex3d(mybezier.anchors[j][i].x, mybezier.anchors[j][i].y, mybezier.anchors[j][i].z);
			glEnd();
		}
		glColor3f(1.0f,1.0f,1.0f);
		glEnable(GL_TEXTURE_2D);
	}

	return TRUE;							// weiter geht's
}

Diese Funktion enthält etwas modifizierten Code, um Ihre Projekte kompatibler zu halten. Es hat nichts mit Bezier Kurven zu tun, aber behebt ein kleines Problem beim Zurückschalten der Auflösung, nach dem Fullscreen-Modus, der bei einigen Grafikkarten auftritt (inklusiver meiner, einer alten ATI Rage PRO und ein paar anderen). Ich hoffe Sie verwenden das von nun an, so dass ich und andere mit ähnlichen Karten euren coolen GL Beispiel Codes korrekt ansehen kann. Um diese Modiifkation vorzunehmen, ändern Sie die KillGLWindow(), stellen Sie sicher, dass Sie DMsaved defniert haben und ändern Sie die eine Zeile in CreateGlWindow() (ist markiert).

GLvoid KillGLWindow(GLvoid)						// Entferne das Fenster korrekt
{
	if (fullscreen)							// Sind wir im Fullscreen Modus?
	{
		if (!ChangeDisplaySettings(NULL,CDS_TEST)) { 		// wenn der Shortcut nicht funktionier ( NEU )
			ChangeDisplaySettings(NULL,CDS_RESET);		// mache es trotzdem (um die Werte aus der Registry zu holen) ( NEU )
			ChangeDisplaySettings(&DMsaved,CDS_RESET);	// ändere sie auf die gespeicherten Einstellungen ( NEU )
		} else {
			ChangeDisplaySettings(NULL,CDS_RESET);		// Wenn es funktioniert, mache weiter ( NEU )
		}

		ShowCursor(TRUE);					// Zeige den Maus-Zeiger
	}

	if (hRC)							// Haben wir einen Rendering Context?
	{
		if (!wglMakeCurrent(NULL,NULL))				// Können wir den DC und RC Kontext freigeben?
		{
			MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		}

		if (!wglDeleteContext(hRC))				// Können wir den RC löschen?
		{
			MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		}
		hRC=NULL;						// Setze RC auf NULL
	}

	if (hDC && !ReleaseDC(hWnd,hDC))				// Können wir DC freigeben?
	{
		MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		hDC=NULL;						// Setze DC auf NULL
	}

	if (hWnd && !DestroyWindow(hWnd))				// Können wir das Fenster zerstören?
	{
		MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		hWnd=NULL;						// Setze hWnd auf NULL
	}

	if (!UnregisterClass("OpenGL",hInstance))			// Können wir die Klasse de-registrieren?
	{
		MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		hInstance=NULL;						// Setze hInstance auf NULL
	}
}
 

Der EnumDisplaySettings() Befehl wurde hinzugefügt um die alten Display-Einstellungen zu speichern. (Teile der alten Grafikkartenprobleme wurden behoben).

// Dieser Code erzeugt unser OpenGL Fenster. Parameter sind:			
// title		- Titel der im Fenster erscheinen soll			
// width		- Breite des GL Fensters oder Fullscreen Modus		
// height		- Höhe des GL Fensters oder Fullscreen Modus		
// bits		- Anzahl der zu benutzenden Farben (8/16/24/32)		
// fullscreenflag	- Use Fullscreen Mode (TRUE) Or Windowed Mode (FALSE)	

BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
	GLuint		PixelFormat;					// Enthält die Ergebnisse nachdem nach was passendem gesucht wurde
	WNDCLASS	wc;						// Fenster Klassen Struktur
	DWORD		dwExStyle;					// erweiterter Fenster-Stil
	DWORD		dwStyle;					// Fenster-Stil
	RECT		WindowRect;					// Enthält die obere linke / untere rechte Eckwerte des Rechtecks
	WindowRect.left=(long)0;					// Setze linken Wert auf 0
	WindowRect.right=(long)width;					// Setze rechten Wert auf  die gewünschte Breite
	WindowRect.top=(long)0;						// Setze den oberen Wert auf 0
	WindowRect.bottom=(long)height;					// Setze unteren Wert auf die gewünschte Höhe

	fullscreen=fullscreenflag;					// Setze das globale Fullscreen Flag

	hInstance		= GetModuleHandle(NULL);		// Ermittle die Instanz für unser Fenster
	wc.style		= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;	// Zeichne neu beim bewegen und eigener DC für's Fenster
	wc.lpfnWndProc		= (WNDPROC) WndProc;			// WndProc behandelt die Nachrichten
	wc.cbClsExtra		= 0;					// Keine extra Fenster-Daten
	wc.cbWndExtra		= 0;					// Keine extra Fenster-Daten
	wc.hInstance		= hInstance;				// Setze die Instanz
	wc.hIcon		= LoadIcon(NULL, IDI_WINLOGO);		// Lade das Standard-Icon
	wc.hCursor		= LoadCursor(NULL, IDC_ARROW);		// Lade den Pfeil-Zeiger
	wc.hbrBackground	= NULL;					// es wird kein Hintergrund für GL benötigt
	wc.lpszMenuName		= NULL;					// Wir wollen kein Menü
	wc.lpszClassName	= "OpenGL";				// Setze den Klassen-Namen

	EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &DMsaved);	// speichere den aktuellen Display Status ( NEU )

	if (fullscreen)							// Fullscreen Modus?
	{
		DEVMODE dmScreenSettings;				// Device Modus
		memset(&dmScreenSettings,0,sizeof(dmScreenSettings));	// Stelle sicher, dass der Speicher geleert ist
		dmScreenSettings.dmSize=sizeof(dmScreenSettings);	// Größe der Devmode Struktur
		dmScreenSettings.dmPelsWidth	= width;		// ausgewählte Screen Breite
		dmScreenSettings.dmPelsHeight	= height;		// ausgewählte Screen Höhe
		dmScreenSettings.dmBitsPerPel	= bits;			// ausgewählte Bits Per Pixel
		dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

	... Code Cut To Save Space (No Further Changes To This Function) ...

	return TRUE;							// Erfolg
}

Alles was ich hier hinzugefügt habe, sind Befehl, um die Fläche zu rotieren, die Auflösung zu erhöhen / runter zu setzen und die Kontrolllinien an und auszuschalten.

int WINAPI WinMain(	HINSTANCE	hInstance,			// Instanz
					HINSTANCE	hPrevInstance,  // vorherige Instanz
					LPSTR		lpCmdLine,	// Kommandozeilen Parameter
					int		nCmdShow)	// Fenster Anzeige Status
{
	MSG		msg;						// Windows Nachrichten Struktur
	BOOL	done=FALSE;						// Bool Variable um die Schleife zu beenden

	// Frage den Benutzer, in welchen Modus er starten will
	if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
	{
		fullscreen=FALSE;					// Fenster-Modus
	}

	// erzeuge unser OpenGL Fenster
	if (!CreateGLWindow("NeHe's Solid Object Tutorial",640,480,16,fullscreen))
	{
		return 0;						// Beende, wenn Fenster nicht erzeugt wurde
	}

	while(!done)							// Schleife die so lange läuft, bis done=TRUE
	{
		if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))		// Wartet eine Nachricht?
		{
			if (msg.message==WM_QUIT)			// Haben wir eine Nachricht zum beenden erhalten?
			{
				done=TRUE;				// Wenn ja done=TRUE
			}
			else						// Wenn nicht, bearbeite die Fenster-Nachrichten
			{
				TranslateMessage(&msg);			// Übersetze die Nachricht
				DispatchMessage(&msg);			// bearbeite die Nachricht
			}
		}
		else							// Wenn da keine Nachrichten sind
		{
			// Zeichne die Szene. Schaue, ob ESC oder DrawGLScene() zum beenden signalisieren
			if ((active && !DrawGLScene()) || keys[VK_ESCAPE])	// Activ? Soll beendet werden?
			{
				done=TRUE;				// ESC oder DrawGLScene Signalisiert, dass Beendet werden soll
			}
			else						// Es ist noch nicht Zeit zum beenden, zeichne Screen neu
			{
				SwapBuffers(hDC);			// Swap Buffers (Double Buffering)
			}


			if (keys[VK_LEFT])	rotz -= 0.8f;		// rotiere links ( NEU )
			if (keys[VK_RIGHT])	rotz += 0.8f;		// rotiere rechts ( NEU )
			if (keys[VK_UP]) {				// mehr Details ( NEU )
				divs++;
				mybezier.dlBPatch = genBezier(mybezier, divs);	// akutalisiere die Fläche
				keys[VK_UP] = FALSE;
			}
			if (keys[VK_DOWN] && divs > 1) {		// weniger Details ( NEU )
				divs--;
				mybezier.dlBPatch = genBezier(mybezier, divs);	// aktualisiere die Fläche
				keys[VK_DOWN] = FALSE;
			}
			if (keys[VK_SPACE]) {				// LEERTASTE invertiert showCPoints ( NEU )
				showCPoints = !showCPoints;
				keys[VK_SPACE] = FALSE;
			}


			if (keys[VK_F1])				// Wurde F1 gedrückt?
			{
				keys[VK_F1]=FALSE;			// Wenn ja, setze Taste auf FALSE
				KillGLWindow();				// Kill unser aktuelles Fenster
				fullscreen=!fullscreen;			// Wechsel zwischen Fullscreen und Fester-Modus
				// Erzeuge unser OpenGL Fenster erneut
				if (!CreateGLWindow("NeHe's Solid Object Tutorial",640,480,16,fullscreen))
				{
					return 0;			// Beenden, wenn das Fenster nicht erzeugt wurde
				}
			}
		}
	}

	// Shutdown
	KillGLWindow();							// Kill das Fenster
	return (msg.wParam);						// Beende das Programm
}

Nun, ich hoffe dieses Tutorial hat etwas Licht in die Sache gebracht und Sie lieben nun Bezier Kurven genauso sehr, wie ich ;-). Wenn Sie dieses Tutorial mögen, werde ich vielleicht ein weiteres Tutorial über NURBS Kurven schreiben, wenn Interesse besteht. Mailen Sie mir und lassen Sie mich wissen, was Sie über dieses Tutorial denken.

Über den Autor: David Nikdel ist zur Zeit 18 und Senior an der Bartow Senior High School. Seine aktuellen Projekte beinhalten ein Research Paper über gekrümmte Flächen in 3D Grpahiken, ein OpenGL basiertes Spiel namens Blazing Sands und faulenzen. Seine Hobbies sind programmieren, Fussball und Paintball. Er wird (hoffentlich) nächstes Jahr an der Georgia Tech anfangen.